/* Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: Module revocation ability support.
 * Author: guyiwen
 * Create: 2020-09-22
 * History:
 * 2020-9-24 by wanglei Add & implenment crl maintaince interface.
 */

#include <keys/system_keyring.h> /* system_verify_data */
#include <linux/crl_revocate.h>
#include <linux/mutex.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/verification.h>
#ifdef CONFIG_RTOS
#include <linux/module.h>
#endif
#include "cmscbb_cms_vrf.h"

#ifdef CONFIG_RTOS_MODULE_SIG_OPTIONS_BUILTIN
#include <internal/rtos_options_builtin.h>
#endif

#define CRL_MODE_DISABLED	0
#define CRL_MODE_WITHADDR	1
#define CRL_MODE_WITHNOADDR	2

#ifdef CONFIG_RTOS_MODULE_SIG_OPTIONS_BUILTIN
#define CRL_NOADDR_FORCE 1
#define CRL_NOADDR_PSS_FORCE 2
#endif

#ifdef CONFIG_EULEROS_CMS_CBB
static unsigned int crl_mode __ro_after_init = CRL_MODE_WITHNOADDR;
#else
static unsigned int crl_mode __ro_after_init = CRL_MODE_DISABLED;
static bool crl_enabled_flag;
#endif

/* Resident kernel CMS verification context */
static CMSCBB_VRF_CTX kernel_cms_vrf_ctx;
DEFINE_MUTEX(cms_vrf_mutex);

#ifdef CONFIG_RTOS
static phys_addr_t kernel_crl_addr;
static unsigned long kernel_crl_size;

/* Initialize crl buffer from crl_addr=size@addr cmdline */
static int __init kernel_crl_setup(char *cmdline_crl_addr)
{
	char *startp = cmdline_crl_addr;
	char *endp = NULL;

	if (!startp)
		return -EINVAL;

	kernel_crl_size = simple_strtoul(startp, &endp, 0);
	if (*endp != '@') {
		pr_err("modsign: invalid crl_addr format, str=%s\n", cmdline_crl_addr);
		return -EINVAL;
	}
	startp = endp + 1;

#ifdef CONFIG_PHYS_ADDR_T_64BIT
	kernel_crl_addr = simple_strtoull(startp, &endp, 0);
#else
	kernel_crl_addr = simple_strtoul(startp, &endp, 0);
#endif
	if (startp == endp) {
		pr_err("modsign: invalid crl_addr format, str=%s\n", cmdline_crl_addr);
		return -EINVAL;
	}

	if (kernel_crl_size == 0 || kernel_crl_size >= MAX_CRL_SIZE) {
		pr_err("modsign: invalid crl size=%lu\n", kernel_crl_size);
		return -EINVAL;
	}

	crl_mode = CRL_MODE_WITHADDR;
	return 0;
}
__setup("crl_addr=", kernel_crl_setup);

static void __init enable_crl_with_noaddr(void)
{
	crl_mode = CRL_MODE_WITHNOADDR;
}

static int __init kernel_crl_noaddr_setup(char *cmdline_crl_addr)
{
	enable_crl_with_noaddr();
	return 0;
}
__setup("crl_noaddr", kernel_crl_noaddr_setup);

static void __init enable_crl_pss_with_noaddr(void)
{
	crl_mode = CRL_MODE_WITHNOADDR;
	crl_enabled_flag = true;
}

static int __init kernel_crl_noaddr_pss_setup(char *cmdline_crl_addr)
{
	enable_crl_pss_with_noaddr();
	return 0;
}
__setup("crl_noaddr_pss", kernel_crl_noaddr_pss_setup);


#ifdef CONFIG_RTOS_EXTRA_CERTIFICATE
static CMSCBB_ERROR_CODE __init kernel_cms_context_init_second(void)
{
	CMSCBB_ERROR_CODE cms_ret = CMSCBB_ERR_CONTEXT_INVALID_PARAM;

	if (get_crl_cert_size_second() > UINT_MAX) {
		pr_err("modsign: second cert size is too large: %lu\n", get_crl_cert_size_second());
		return CMSCBB_ERR_CONTEXT_INVALID_PARAM;
	}

	if (get_crl_cert_second() && get_crl_cert_size_second()) {
		cms_ret = CmscbbVerifyAddCert(kernel_cms_vrf_ctx, get_crl_cert_second(),
					      (CVB_UINT32)get_crl_cert_size_second());
		if (cms_ret)
			pr_err("modsign: CmscbbVerifyAddCert failed second cert, cms_ret: 0x%x\n",
				CMS_RET_FORMAT(cms_ret));

		return cms_ret;
	}

	return 0;
}
#endif

static CMSCBB_ERROR_CODE try_init_crl_withaddr(void)
{
	CMSCBB_ERROR_CODE cms_ret = CMSCBB_ERR_CONTEXT_INVALID_PARAM;
	void __iomem *mmap_addr = NULL;

	if (crl_mode == CRL_MODE_WITHADDR && kernel_crl_size > 0 && kernel_crl_addr > 0) {
		mmap_addr = ioremap(kernel_crl_addr, kernel_crl_size);
		if (!mmap_addr) {
			pr_err("modsign: map crl data from physical address fail\n");
			return CMSCBB_ERR_CONTEXT_INVALID_PARAM;
		}
		copyto_crl_ondevice_buf(mmap_addr, kernel_crl_size);
		set_crl_ondevice_size(kernel_crl_size);
		iounmap(mmap_addr);
		if (get_crl_ondevice_size() > UINT_MAX) {
			pr_err("modsign: crl size is too large: %zu\n", get_crl_ondevice_size());
			return CMSCBB_ERR_CONTEXT_INVALID_PARAM;
		}

		cms_ret = CmscbbVerifyAddCrl(kernel_cms_vrf_ctx, get_crl_ondevice_buf(),
					     (CVB_UINT32)get_crl_ondevice_size());
		if (cms_ret) {
			pr_err("modsign: CmscbbVerifyAddCrl failed to add crl, cms_ret: 0x%x\n",
				CMS_RET_FORMAT(cms_ret));
			return cms_ret;
		}
		crl_enabled_flag = true;
		pr_info("modsign: crl initialized\n");
	}
	return 0;
}

#ifdef CONFIG_RTOS_MODULE_SIG_OPTIONS_BUILTIN
static void __init builtin_crl_pss_force_init(void)
{
	if (builtin_option_crl_flags == CRL_NOADDR_PSS_FORCE) {
		pr_info("modsign: builtin_option_crl_flags is set to 2, enable crl_pss with noaddr mode.\n");
		enable_crl_pss_with_noaddr();
	}
}

static void __init builtin_crl_force_init(void)
{
	if (builtin_option_crl_flags == CRL_NOADDR_FORCE) {
		pr_info("modsign: builtin_option_crl_flags is set to 1, enable crl_pkcs with noaddr mode.\n");
		enable_crl_with_noaddr();
	}
}

static void __init builtin_sig_force_init(void)
{
	if (builtin_option_sig != 0) {
		pr_info("modsign: builtin_option_sig is set to non-zero, enable sig_enforce.\n");
		set_module_sig_enforced();
	}
}
#endif
#endif /* CONFIG_RTOS */

/* Initialize cms context when kernel startup */
static int __init kernel_cms_context_init(void)
{
	CMSCBB_ERROR_CODE cms_ret;
	CmscbbCtxCreateParams params = {0};

#ifdef CONFIG_RTOS_MODULE_SIG_OPTIONS_BUILTIN
	builtin_crl_pss_force_init();
	builtin_crl_force_init();
	builtin_sig_force_init();
#endif

	if (!get_crl_cert() || crl_mode == CRL_MODE_DISABLED)
		return -EBADMSG;

	params.st_size = sizeof(params);

	cms_ret = CmscbbVerifyCreateCtx(&kernel_cms_vrf_ctx, &params);
	if (cms_ret) {
		pr_err("modsign: CmscbbVerifyCreateCtx failed to create CMS context, cms_ret: 0x%x\n",
			CMS_RET_FORMAT(cms_ret));
		goto err;
	}

	cms_ret = CmscbbVerifyAddCert(kernel_cms_vrf_ctx, get_crl_cert(), get_crl_cert_size());
	if (cms_ret) {
		pr_err("modsign: CmscbbVerifyAddCert failed to add cert, cms_ret: 0x%x\n",
			CMS_RET_FORMAT(cms_ret));
		goto err;
	}

#ifdef CONFIG_RTOS_EXTRA_CERTIFICATE
	if (kernel_cms_context_init_second() != 0)
		goto err;
#endif

	pr_info("modsign: cms context initialized\n");

#ifdef CONFIG_RTOS
	/* Initialize crl buffer and add crl to context */
	if (try_init_crl_withaddr() != 0)
		goto err;
#endif

	return 0;

err:
	/* If init fail, reset kernel_cms_vrf_ctx to NULL */
	if (kernel_cms_vrf_ctx) {
		CmscbbVrfCtxFree(kernel_cms_vrf_ctx);
		kernel_cms_vrf_ctx = NULL;
	}
	return -EINVAL;
}
late_initcall(kernel_cms_context_init);

#ifdef CONFIG_EULEROS_CMS_CBB
int mod_update_crl(const char *crl, size_t size)
#else
int kernel_update_crl(const char *crl, size_t size)
#endif
{
	CMSCBB_ERROR_CODE cms_ret;
	int err = -EINVAL;

	if (crl_mode == CRL_MODE_DISABLED)
		return 0;

	mutex_lock(&cms_vrf_mutex);
	if (!kernel_cms_vrf_ctx) {
		mutex_unlock(&cms_vrf_mutex);
		return err;
	}

	cms_ret = CmscbbResetCrl(kernel_cms_vrf_ctx);
	if (cms_ret) {
		mutex_unlock(&cms_vrf_mutex);
		pr_err("modsign: CmscbbResetCrl failed to reset crl in cms_vrf, cms_ret: 0x%x\n",
			CMS_RET_FORMAT(cms_ret));
		return err;
	}

	cms_ret = CmscbbVerifyAddCrl(kernel_cms_vrf_ctx, crl, size);
	if (cms_ret) {
		err = -EKEYREJECTED;
		pr_err("modsign: CmscbbVerifyAddCrl failed to update crl, cms_ret: 0x%x\n",
			CMS_RET_FORMAT(cms_ret));
	} else {
		err = 0;
#ifdef CONFIG_EULEROS_CMS_CBB
			pr_info("modsign: crl is updated");
#else
		if (crl_mode == CRL_MODE_WITHNOADDR && !crl_enabled_flag) {
			crl_enabled_flag = true;
			pr_info("modsign: crl initialized\n");
		}
#endif
	}

	mutex_unlock(&cms_vrf_mutex);
	return err;
}
#ifdef CONFIG_EULEROS_CMS_CBB
EXPORT_SYMBOL_GPL(mod_update_crl);
#else
EXPORT_SYMBOL_NS(kernel_update_crl, HW_RTOS_NS);
#endif

static int kernel_verify_codesign_cms(const void *mod, size_t modlen,
				const void *sig, size_t siglen)
{
	CMSCBB_ERROR_CODE cms_ret;
	CVB_INT32 cms_result = CVB_FALSE;
	char *tmp_sig = NULL;
	int err = -ENOKEY;

	if (siglen == 0 || siglen > MAX_CMS_SIZE) {
		pr_err("modsign: invalid signature length\n");
		return -EINVAL;
	}

	tmp_sig = kmalloc(siglen, GFP_KERNEL);
	if (!tmp_sig)
		return -ENOMEM;
	memcpy(tmp_sig, sig, siglen);

	mutex_lock(&cms_vrf_mutex);
	cms_ret = CmscbbResetCrl(kernel_cms_vrf_ctx);
	if (cms_ret) {
		pr_err("modsign: CmscbbResetCrl failed, cms_ret: 0x%x\n", CMS_RET_FORMAT(cms_ret));
		goto out;
	}

	if (get_crl_ondevice_size() > 0) {
		cms_ret = CmscbbVerifyAddCrl(kernel_cms_vrf_ctx, get_crl_ondevice_buf(),
					     get_crl_ondevice_size());
		if (cms_ret) {
			pr_err("modsign: CmscbbVerifyAddCrl failed to add current crl, cms_ret: 0x%x\n",
			       CMS_RET_FORMAT(cms_ret));
			goto out;
		}
	}

	cms_ret = CmscbbVerifyDetachSignatureBegin(kernel_cms_vrf_ctx, tmp_sig, siglen);
	if (cms_ret) {
		pr_err("modsign: CmscbbVerifyDetachSignatureBegin failed, cms_ret: 0x%x\n",
			CMS_RET_FORMAT(cms_ret));
		goto out;
	}

	cms_ret = CmscbbVerifyDetachSignatureUpdate(kernel_cms_vrf_ctx, mod, modlen);
	if (cms_ret) {
		pr_err("modsign: CmscbbVerifyDetachSignatureUpdate failed, cms_ret: 0x%x\n",
			CMS_RET_FORMAT(cms_ret));
		goto out;
	}

	cms_ret = CmscbbVerifyDetachSignatureFinal(kernel_cms_vrf_ctx, &cms_result);
	if (cms_ret == 0 && cms_result == CVB_TRUE)
		err = 0;
	else
		pr_err("modsign: VerifySignatureFinal failed, cms_ret: 0x%x, cms_result: %d\n",
			CMS_RET_FORMAT(cms_ret), cms_result);
out:
	CmscbbResetCert(kernel_cms_vrf_ctx, CVB_FALSE);
	mutex_unlock(&cms_vrf_mutex);
	kfree(tmp_sig);
	return err;
}

#ifdef CONFIG_EULEROS_CMS_CBB
int mod_verify_cms_data(const void *mod, unsigned long modlen,
			const void *sig, size_t sig_len)
{
	return kernel_verify_codesign_cms(mod, modlen,
		sig, sig_len);
}
EXPORT_SYMBOL_GPL(mod_verify_cms_data);
#else
int kernel_verify_cms_data(const void *mod, unsigned long modlen,
			const void *sig, size_t sig_len)
{
	if (crl_enabled_flag)
		return kernel_verify_codesign_cms(mod, modlen,
			sig, sig_len);
	else
		return verify_pkcs7_signature(mod, modlen, sig, sig_len,
				      NULL,
				      VERIFYING_UNSPECIFIED_SIGNATURE,
				      NULL, NULL);
}
EXPORT_SYMBOL_NS(kernel_verify_cms_data, HW_RTOS_NS);
#endif
